/*
 * Copyright (c) 2018-2023 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "subcmd.h"
#include "subcmd.admin.h"

static uid_t suidStartup;
static struct clusterConf *sptrClusterConf = NULL;

static void startup_allnodes(bool_t, const char *);
static void startup_mynode(const char *);
static void startup_remotenode(char *, int);
static int get_quickpool_env(const char *);
static int set_startup_uid(void);

/**
 * @brief       qp jm start handler, to start jm on specified nodes.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      2             #3: argument has syntax error
 * @retval      3             #4: failed to start jm
 *
 * @note        For local node, issue jm -d env_dir to start it up.
 *              For remote node, issue ssh sh -c jm -d env_dir to start it up.
 *              We could also load dynamic node and start them up.
 */
int
startup(int argc, char **argv)
{
    bool_t fOpt = FALSE;
    while (TRUE) {
        int option_index = 0;
        static struct option long_options[] = {
            {"help", no_argument, 0, 'h'},
            {"force", no_argument, 0, 'f'},
            {NULL, 0, 0, 0}
        };

        int opt;
        opt = getopt_long(argc, argv, "hf", long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
        case 'f':
            fOpt = TRUE;
            break;
        case 'h':
            admin_usage(ADMIN_JM_STARTUP);
            exit(0);
        case '?':
        default:
            admin_usage(ADMIN_JM_STARTUP);
            exit(1);
        }
    }

    if (0 > set_startup_uid()) { // only root can start up JM.
        fprintf(stderr, "Not authorized to start up as root\n");
        exit(3);
    }

    char *strMyName;
    strMyName = qp_getmyhostname();
    if (NULL == strMyName) {
        exit(2);
    }

    if (optind == argc) { // no node specified, start up myself.

        startup_mynode(strMyName);
        exit(0);
    } else if (optind == argc - 1 && strcmp(argv[optind], ALL_STRING) == 0) { // start up all.

        startup_allnodes(fOpt, strMyName);
        exit(0);
    }

    if (0 > get_quickpool_env(strMyName)) { // read configuration file.
        exit(2);
    }

    LIST_T *dynamicNodeList=NULL;
    //compare specific host
    for (; optind < argc; optind++) {

        if (0 == strcmp(strMyName, argv[optind])) { // start myself.
            startup_mynode(strMyName);
            continue;
        }

        bool_t startFlag=FALSE;
        unsigned int i;
        for (i=0; i<sptrClusterConf->num_hosts; i++) {

            if (0 == strcmp(sptrClusterConf->hosts[i].host, argv[optind])) {

                startup_remotenode(argv[optind], !fOpt);
                startFlag = TRUE;
                break;
            }               
        }            

        if (startFlag) {
            continue;
        }

        // node is not in configuration file, let's check dynamic nodes.
        if (NULL == dynamicNodeList) {

            dynamicNodeList = gf_list_make("dynamic node list");
            if (0 > load_dynamic_node(dynamicNodeList)) {
                gf_list_free(dynamicNodeList, free_dynamic_node);
                dynamicNodeList = NULL;
                continue;
            }
        }

        LIST_ITERATOR_T iter;
        gf_listiterator_link(&iter, dynamicNodeList);

        struct dynamicNode *dynNode;
        for (dynNode = (struct dynamicNode *)gf_listiterator_curentry(&iter);
             !gf_listiterator_isend(&iter);
             gf_listiterator_getnext(&iter, (LIST_T **)&dynNode)) {

            if (NULL == dynNode || NULL == dynNode->node_req) {
                continue;
            }

            struct request *nodeReq = dynNode->node_req;

            bool_t started=FALSE;
            for (i=0; i<nodeReq->number; i++) {
                switch (nodeReq->keyvalues[i].key) {
                case REQUEST_KEY_HOSTNAME:
                    started = TRUE;
                    break;
                default:
                    break;
                }

                if (started) {

                    if (0 == strcmp(argv[optind], nodeReq->keyvalues[i].value)) {
                        startup_remotenode(argv[optind], !fOpt);
                        startFlag = TRUE;
                    }

                    break;                        
                }
            }
        }

        if (!startFlag) {
            // node is not found in my cluster.
            fprintf(stderr, "Node <%s> is not belong to this cluster.\n", argv[optind]);
        }
    }

    exit(0);
} // end function startup

/* ------------------------------------------------
 *
 *  below are static functions used in this file
 *
 * ------------------------------------------------ */

/**
 * @brief       ready configuration file and check if suitable
 *              to issue start jm on this node.
 *
 * @param[in]   name          #1: my host name
 *
 * @retval      0             #1: ok to issue
 * @retval      -1            #2: not ok, cannot ready file or I'm not in master node list
 */
static int
get_quickpool_env(const char *name)
{
    char qpSharedFile[PATH_MAX];
    snprintf(qpSharedFile, PATH_MAX, "%s/qp.shared", baseParams[BASE_CONFDIR].string_value);

    if (access(qpSharedFile, R_OK)) {
        fprintf(stderr, "%s: access file <%s> failed: %m.\n", __func__, qpSharedFile);
        return -1;
    }

    struct sharedConf *mySharedConf;
    mySharedConf = readconf_shared(qpSharedFile);
    if (NULL == mySharedConf) {
        fprintf(stderr, "%s: failed to read file <%s>.\n", __func__, qpSharedFile);
        return -1;
    }

    char qpClusterFile[PATH_MAX];
    snprintf(qpClusterFile, PATH_MAX, "%s/qp.cluster.%s",
             baseParams[BASE_CONFDIR].string_value, mySharedConf->cluster_name);

    sptrClusterConf = readconf_cluster(qpClusterFile);
    if (NULL == sptrClusterConf) {
        fprintf(stderr, "%s: failed to read file <%s>.\n", __func__, qpClusterFile);
        return -1;
    }

    bool_t masterMe = FALSE;

    char *masterList = copy_string(g_masterlist);
    char *list = masterList;
    char *host;
    char *savedHost;
    while ((host = strtok_r(masterList, " ", &savedHost)) != NULL) {
        masterList = NULL;

        if (issame_host(name, host)) {
            masterMe = TRUE;
        }
    }
    FREEUP(list);

    if (!masterMe) { // I'm not in master node list.

        fprintf(stderr, "Must startup remote host on master or master candidate host.\n");
        return -1;
    }

    return 0;
} // end function get_quickpool_env

static int
set_startup_uid(void)
{
    suidStartup = getuid();

    if (suidStartup == 0) {
	return 0;
    }

    return -1;
} // end function set_startup_uid

/**
 * @brief       startup jm on all cluster nodes.
 *
 * @param[in]   foption       #1: if ask for confirm
 * @param[in]   name          #2: my host name
 *
 */
static void
startup_allnodes(bool_t foption, const char *name)
{
    char msg[PATH_MAX+1];

    bool_t confirm = FALSE;
    if (!foption) { // ask for confirm to start jm on all nodes.

        snprintf(msg, PATH_MAX, "Do you really want to start up JM on all hosts ? [y/n] ");
        confirm = (!askfor_confirm(msg));
    }

    if (0 > get_quickpool_env(name)) {
	return;
    }

    /* First, try to startup myself */
    char *masterList = copy_string(g_masterlist);
    char *list = masterList;
    char *host;
    char *savedHost;
    while ((host = strtok_r(masterList, " ", &savedHost)) != NULL) {
        masterList = NULL;

        if (issame_host(name, host)) {
            startup_mynode(name);
            break;
        }
    }
    FREEUP(list);

    /* Then, startup remote host defined in cluster file. */
    unsigned int i;
    for (i=0; i<sptrClusterConf->num_hosts; i++) {

        if (CLIENT == sptrClusterConf->hosts[i].server) { // ignore client node.
            continue;
        }

        if (0 == strcmp(name, sptrClusterConf->hosts[i].host)) { // already started up myself.
            continue;
        }

        if (NULL == sptrClusterConf->hosts[i].host_type
            || (strncmp(sptrClusterConf->hosts[i].host_type, "NT", 2) != 0)) {

            startup_remotenode(sptrClusterConf->hosts[i].host, confirm);
        }
    }

    LIST_T *dynamicNodeList;
    dynamicNodeList = gf_list_make("dynamic node list");

    load_dynamic_node(dynamicNodeList);

    if (0 == LIST_NUM_ENTS(dynamicNodeList)) { // there's no dyanmic node, stop at here.
        gf_list_free(dynamicNodeList, free_dynamic_node);
        return;
    }

    /* Finally, startup remote dynamic host. */
    LIST_ITERATOR_T iter;
    gf_listiterator_link(&iter, dynamicNodeList);

    struct dynamicNode *dynNode;
    for (dynNode = (struct dynamicNode *)gf_listiterator_curentry(&iter);
         !gf_listiterator_isend(&iter); gf_listiterator_getnext(&iter, (LIST_T **)&dynNode)) {

        if (NULL == dynNode || NULL == dynNode->node_req) {
            continue;
        }

        struct request *nodeReq = dynNode->node_req;

        bool_t started=FALSE;
        for (i=0; i<nodeReq->number; i++) {
            switch (nodeReq->keyvalues[i].key) {
            case REQUEST_KEY_HOSTNAME:
                started = TRUE;
                break;
            default:
                break;
            }

            if (started) {
                startup_remotenode(nodeReq->keyvalues[i].value, confirm);
                break;
            }
        }
    }

    return;
} // end function startup_allnodes

/**
 * @brief       execute jm in command line to start it up.
 *
 * @param[in]   uid           #1: who to start jm up
 * @param[in]   myargv        #2: execute jm argument
 *
 * @retval      0             #1: succeed
 * @retval      -1            #2: failed to execute jm
 */
static int
execute_jm(int uid, char **myargv)
{
    pid_t pid;
    pid = fork();
    if (0 > pid) {
        perror("fork");
	return -1;
    } else if (0 < pid) { // parent
        STATUS_WAIT_T status;

        while (wait(&status) != pid) ;

        if (!WEXITSTATUS(status)) { // command succeed.
            return 0;
        }

        return -1;
    }

    // child, to execute command
    if (0 > setuid(uid)) { // change to uid.
	perror("setuid");
	exit(1);
    }

    execvp(myargv[0], myargv);
    perror(myargv[0]);

    exit(2);
} // end function execute_jm

/**
 * @brief       startup jm on myself.
 *
 * @param[in]   name          #1: my host name
 *
 */
static void
startup_mynode(const char *name)
{
    fprintf(stderr, "Starting up JM on <%s> ...... ", name);
    fflush(stderr);

    char *myargv[10];
    myargv[1] = NULL;
    myargv[2] = NULL;
    myargv[3] = NULL;
    myargv[4] = NULL;

    char daemonPath[PATH_MAX];
    snprintf(daemonPath, PATH_MAX, "%s/jm", baseParams[BASE_SERVERDIR].string_value);

    myargv[0] = daemonPath;

    char *envDir = getenv((char *)"ENVDIR");
    if (NULL != envDir) {
        myargv[1] = (char *)"-d";
        myargv[2] = envDir;
    }

    if (0 == execute_jm(suidStartup, myargv)) {
        fprintf(stderr, "%s\n", "done");
    }

    fflush(stderr);
    return;
} // end function startup_mynode

/**
 * @brief       startup jm on remote nodes.
 *
 * @param[in]   host          #1: remote host name
 * @param[in]   ask           #2: ask for confirm
 *
 */
static void
startup_remotenode(char *host, int ask)
{
    char msg[2*PATH_MAX+1];
    int n = 0;
    n += snprintf(msg, 2*PATH_MAX, "Start up JM on");

    if (ask) {

        snprintf(msg + n, 2*PATH_MAX - n, " <%s> ? [y/n] ", host);
        if (!askfor_confirm(msg)) {
            return;
        }
    }

    int cc=0;
    char *myargv[10];
    myargv[cc++] = (char *)"ssh";
    struct hostent *hent;
    hent = gf_gethostbyname(host);
    if (NULL == hent) {
        myargv[cc++] = host;
    } else {
        myargv[cc++] = inet_ntoa(*(struct in_addr*)hent->h_addr_list[0]);
    }

    char *envDir;
    envDir = getenv("ENVDIR");
    if (NULL == envDir) { // ENVDIR is not set.
       fprintf(stderr, "Environment variable ENVDIR not set. Service on remote host cannot be startup.\n");
       fflush(stderr);
       return;
    }

    myargv[cc++] = (char *)"/bin/sh ";
    myargv[cc++] = (char *)"-c ";

    if (PATH_MAX < strlen(envDir)) {
        fprintf(stderr,"ENVDIR <%s> is longer than <%d>.\n", envDir, PATH_MAX);
        exit(3);
    }

    memset(msg,0,sizeof(msg));
    strncat(msg, "'ENVDIR=", 2*PATH_MAX);
    strncat(msg, envDir, 2*PATH_MAX);

    char *binEnvDir;
    binEnvDir = getenv("BINDIR");
    if (NULL != binEnvDir) {

        if (PATH_MAX < strlen(binEnvDir)) {
            fprintf(stderr,"BINDIR <%s> is longer than <%d>.\n", binEnvDir, PATH_MAX);
            exit(3);
        }

        strncat(msg, "; BINDIR=", 2*PATH_MAX);
        strncat(msg, binEnvDir, 2*PATH_MAX);
    }

    char *sbinEnvDir;
    sbinEnvDir = getenv("SERVERDIR");
    if (NULL != sbinEnvDir) {

        if (PATH_MAX < strlen(sbinEnvDir)) {
            fprintf(stderr,"SERVERDIR <%s> is longer than <%d>.\n", sbinEnvDir, PATH_MAX);
            exit(3);
        }

        strncat(msg, "; SERVERDIR=", 2*PATH_MAX);
        strncat(msg, sbinEnvDir, 2*PATH_MAX);
    }

    char *libPathEnv;
    libPathEnv = getenv("LD_LIBRARY_PATH");
    if (NULL != libPathEnv) {

        if (PATH_MAX < strlen(libPathEnv)) {
            fprintf(stderr,"LD_LIBRARY_PATH <%s> is longer than <%d>.\n", libPathEnv, PATH_MAX);
            exit(3);
        }

        strncat(msg, "; LD_LIBRARY_PATH=", 2*PATH_MAX);
        strncat(msg, libPathEnv, 2*PATH_MAX);
    }

    strncat(msg, "; export ENVDIR; export BINDIR; export SERVERDIR; export LD_LIBRARY_PATH",
            2*PATH_MAX);

    /* env BINDIR cannot be loaded somewhat. */
    strncat(msg, "; $BINDIR/qp jm start'", 2*PATH_MAX);
    myargv[cc++] = msg;
    myargv[cc] = NULL;

    execute_jm(getuid(), myargv);
    fflush(stderr);

    return;
} // end function startup_remotenode
